/*
 * Copyright 2013-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.gateway.handler;

import java.util.function.Function;

import reactor.core.publisher.Mono;

import org.springframework.cloud.gateway.config.GlobalCorsProperties;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.core.env.Environment;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
import org.springframework.web.server.ServerWebExchange;

import static org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping.ManagementPortType.DIFFERENT;
import static org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping.ManagementPortType.DISABLED;
import static org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping.ManagementPortType.SAME;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;

/**
 * @author Spencer Gibb
 */
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {

	private final FilteringWebHandler webHandler;

	private final RouteLocator routeLocator;

	private final Integer managementPort;

	private final ManagementPortType managementPortType;

	public RoutePredicateHandlerMapping(FilteringWebHandler webHandler,
										RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties,
										Environment environment) {
		this.webHandler = webHandler;
		this.routeLocator = routeLocator;

		this.managementPort = getPortProperty(environment, "management.server.");
		this.managementPortType = getManagementPortType(environment);
		setOrder(1);
		setCorsConfigurations(globalCorsProperties.getCorsConfigurations());
	}

	/**
	 * 根据提供的环境对象，获取管理端口类型。
	 *
	 * @param environment 环境对象，包含服务器端口信息
	 * @return 返回管理端口类型，可以是DISABLED、SAME或DIFFERENT
	 */
	private ManagementPortType getManagementPortType(Environment environment) {
		// 获取服务器端口(如果用户没有配置server.port)，可能读出来的是null
		Integer serverPort = getPortProperty(environment, "server.");
		// 在调用该方法之前：this.managementPort = getPortProperty(environment, "management.server.");
		// 如果用户没有配置management.server.port，则会读取到null
		// 如果管理端口为负数，则返回DISABLED , 这个小于0我有点没看懂，哈哈，后续再网上搜搜
		if (this.managementPort != null && this.managementPort < 0) {
			return DISABLED;
		}
		// 根据管理端口和服务器端口的关系，返回SAME或DIFFERENT
		// 用户没有设置management.server.port
		return ((this.managementPort == null
				// 用户没有配置server.port，但是management.server.port设置了8080)
				|| (serverPort == null && this.managementPort.equals(8080))
				// 用户设置了management.server.port =0 或 >0 并且与服务器端口相同，)
				|| (this.managementPort != 0 && this.managementPort.equals(serverPort)))
				? SAME : DIFFERENT);
	}


	private static Integer getPortProperty(Environment environment, String prefix) {
		return environment.getProperty(prefix + "port", Integer.class);
	}

	@Override
	protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
		// exchange => 在HttpWebHandlerAdapter中创建出来的，
		// HttpWebHandlerAdapter是由WebServerManager构造函数带过来的
		// HttpWebHandlerAdapter => 是在WebFlux中通过Netty创建WebServer时带进去的，
		// 参见代码 org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext#createWebServer
		// 进而可以看到，在NettyReactiveWebServerFactory中创建的WebServer中，
		// 参见代码 org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory#getWebServer
		// 如果设置了管理端口并且与服务器端口不同，则不处理管理端口上的请求
		if (this.managementPortType == DIFFERENT &&
				this.managementPort != null &&
				exchange.getRequest().getURI().getPort() == this.managementPort) {
			return Mono.empty();
		}
		// GATEWAY_HANDLER_MAPPER_ATTR 常量值是 "gatewayHandlerMapper"
		// getSimpleName() 返回当前类的简单名称，这里直接返回的是固定的，不是动态取的 "RoutePredicateHandlerMapping"
		// 把RoutePredicateHandlerMapping这个handlerMaping放入exchange上下文中
		exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());
		// lookupRoute 就是从配置文件中查找匹配的路由信息，然后返回一个Mono<Route>对象
		return lookupRoute(exchange)
				// flatMap = "找到东西后，接着干什么"
				.flatMap((Function<Route, Mono<?>>) r -> {
					// 1. 清理临时属性
					exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
					// 2. 记录调试日志
					if (logger.isDebugEnabled()) {
						logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r);
					}
					// 3. 保存路由信息到exchange中（重要！）
					exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
					// 4. 返回实际的处理器
					return Mono.just(webHandler);
				})
				// switchIfEmpty = "如果什么都没找到，那就..."
				.switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
					// 1. 清理临时属性
					exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
					// 2. 记录跟踪日志
					if (logger.isTraceEnabled()) {
						logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
					}
				})));
	}

	@Override
	protected CorsConfiguration getCorsConfiguration(Object handler,
													 ServerWebExchange exchange) {
		// TODO: support cors configuration via properties on a route see gh-229
		// see RequestMappingHandlerMapping.initCorsConfiguration()
		// also see
		// https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/test/java/org/springframework/web/cors/reactive/CorsWebFilterTests.java
		return super.getCorsConfiguration(handler, exchange);
	}

	// TODO: get desc from factory?
	private String getExchangeDesc(ServerWebExchange exchange) {
		StringBuilder out = new StringBuilder();
		out.append("Exchange: ");
		out.append(exchange.getRequest().getMethod());
		out.append(" ");
		out.append(exchange.getRequest().getURI());
		return out.toString();
	}

	/**
	 * 确定当前 HandlerMapping 是不是满足当前请求
	 *
	 * <p>
	 * 从所有配置的路由中找出第一个匹配当前请求的路由
	 * </p>
	 *
	 * @param exchange 当前请求对象
	 * @return Mono<Route> 对象，表示匹配的路由信息
	 */
	protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
		// 路由信息从哪里来？就是从我们的配置文件（application.yml）中去拿
		// 通过点击getRoutes是一个接口，跟踪：RouteDefinitionRouteLocator
		// RouteLocator在GatewayAutoConfiguration注入了RouteDefinitionRouteLocator
		// 1、遍历 CachingRouteLocator 中的所有 Route
		return this.routeLocator.getRoutes()
				// concatMap ：顺序转换和匹配，如果前面的Mono<Route>为空（ 参见onErrorResume 里面返回的Mono.empty()），则继续下一个Mono<Route>的匹配
				// 从 routeLocator.getRoutes() 出来的路由列表，concatMap 会严格按照配置顺序一个一个处理
				// routes:
				// 	  - id: route1  # Path=/api/**
				// 	  - id: route2  # Path=/flowable/**  ← 这个匹配当前请求
				//    - id: route3  # Path=/test/**
				// 假设我们从配置文件中读取到的配置是上面的
				.concatMap(route -> {
							// 当route => route1 时
							return Mono.just(route) // 将route包装成Mono<Route>对象,主要是用于
									// filterWhen：这是一个异步过滤操作符。它接受一个函数，该函数返回一个Mono<Boolean>。
									.filterWhen(r -> {
										// 将当前路由的id放入到exchange中，以便后续使用
										exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
										// 前面我们从配置文件中拿到了我们的断言配置，r.getPredicate()就是取的是配置文件中配置的哪些断言
										// 使用 Route 的所有 Predicate 都去执行 exchange（请求）是否满足条件，不满足一个 Predicate，就会使用下一条 Route
										// 这个r.getPredicate()返回的数据结构，看不懂。网上说是Either 模式 和 组合模式的结合，看不懂，看不懂
										// 反正意思就是返回的是一个Mono<Boolean>对象，表示当前路由是否满足条件
										// 假设这一步返回了false。那就继续遍历concatMap里面的下一个route
										//
										return r.getPredicate().apply(exchange);
									})
									// 每个路由的断言错误不会影响其他路由
									// 记录错误
									.doOnError(e -> logger.error("Error applying predicate for route: " + route.getId(), e))
									// 处理其他路由
									.onErrorResume(e -> Mono.empty());
						}
				)
				// .defaultIfEmpty() put a static Route not found   // 源码就已经注释了
				// or .switchIfEmpty() // 源码就已经注释了
				// .switchIfEmpty(Mono.<Route>empty().log("noroute")) // 源码就已经注释了
				// 从所有满足条件路由中找出第一个的路由
				.next()
				// TODO: error handling
				.map(route -> {
					if (logger.isDebugEnabled()) {
						logger.debug("Route matched: " + route.getId());
					}
					// 空实现
					validateRoute(route, exchange);
					return route;
				});

		/*
		 * TODO: trace logging if (logger.isTraceEnabled()) {
		 * logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); }
		 */
	}

	/**
	 * Validate the given handler against the current request.
	 * <p>
	 * The default implementation is empty. Can be overridden in subclasses, for example
	 * to enforce specific preconditions expressed in URL mappings.
	 *
	 * @param route    the Route object to validate
	 * @param exchange current exchange
	 * @throws Exception if validation failed
	 */
	@SuppressWarnings("UnusedParameters")
	protected void validateRoute(Route route, ServerWebExchange exchange) {
	}

	protected String getSimpleName() {
		return "RoutePredicateHandlerMapping";
	}

	public enum ManagementPortType {
		/**
		 * 管理端口被禁用（公司没有设立专门的运维部门）
		 */
		DISABLED,
		/**
		 * 管理端口和业务端口是同一个（运维和业务共用同一个入口）
		 */
		SAME,
		/**
		 * 管理端口和业务端口是不同的（运维有自己独立的入口）
		 */
		DIFFERENT;
	}

}
